package jaux.tank.service.system.impl;

import static jaux.tank.bean.enumeration.AttachmentSaveTo.DB;
import static jaux.tank.bean.enumeration.AttachmentSaveTo.FILESYSTEM;
import static jaux.tank.bean.enumeration.AttachmentSaveTo.S3;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.Optional;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;

import jaux.tank.admin.config.properties.TankProperties;
import jaux.tank.admin.core.filestorage.AwsFileStorage;
import jaux.tank.bean.core.ShiroUser;
import jaux.tank.bean.entity.system.Attachment;
import jaux.tank.bean.entity.system.AttachmentBase;
import jaux.tank.dao.system.AttachmentRepository;
import jaux.tank.service.system.AttachmentService;
import jaux.tank.shiro.ShiroKit;
import jaux.tank.utils.DateUtil;
import jaux.tank.utils.ObjectId;
import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
public class AttachmentServiceImpl implements AttachmentService{

	@Autowired
	private AttachmentRepository attachmentRepository;
	
	@Autowired
	private TankProperties tankProps;
	
	@Autowired
	private AwsFileStorage awsFileStorage;
	
	
	
	@Override
	public List<AttachmentBase> listAttachments() {
		return attachmentRepository.findByOrderByUploadTimeDesc();
	}

	@Override
	public Attachment save(Attachment attachment, File attachmentFile) throws IOException {
		if(FILESYSTEM.name().equalsIgnoreCase(attachment.getSaveTo())
				&& !tankProps.isWorkAttachmentSaveToS3()) {
			saveAttachmentToFilesystem(attachment, attachmentFile);
		}else if(S3.name().equalsIgnoreCase(attachment.getSaveTo()) 
					|| (FILESYSTEM.name().equalsIgnoreCase(attachment.getSaveTo())
						&& tankProps.isWorkAttachmentSaveToS3())) {
			try {
				saveAttachmentToS3(attachment, attachmentFile);
			}catch (Exception ex) {
				log.warn("save attachment to S3 failed, falling back to save attachment to filesystem");
				saveAttachmentToFilesystem(attachment, attachmentFile);
			}
    	}else if(DB.name().equalsIgnoreCase(attachment.getSaveTo())) {
    		try {
    			saveAttachmentToDB(attachment, attachmentFile);
    		}catch (Exception ex) {
    			log.warn("save attchment to db failed, falling back to save attachment to filesystem");
    			saveAttachmentToFilesystem(attachment, attachmentFile);
    		}
    		
    	}
		return attachmentRepository.save(attachment);
	}

	private void saveAttachmentToDB(Attachment attachment, File attachmentFile) throws IOException {
		if(attachment.getFileSize()> tankProps.getWorkAttachmentLimitToDb()){
			throw new IOException("attachment is too big to save to db");
		}
		attachment.setSaveTo(DB.name());
		attachment.setFileContent(FileUtils.readFileToByteArray(attachmentFile));
	}

	private void saveAttachmentToS3(Attachment attachment, File attachmentFile) throws IOException {
		String path = awsFileStorage.saveStream(attachment.getFileName(), new FileInputStream(attachmentFile), null);
		attachment.setSavePath(path);
		attachment.setSaveTo(S3.name());
	}

	private void saveAttachmentToFilesystem(Attachment attachment, File attachmentFile) throws IOException {
		File savePathRoot = new File(tankProps.getWorkAttachmentSavedir());
		if(!savePathRoot.exists()) {
			savePathRoot.mkdirs();
		}
		Path savePath = savePathRoot.toPath().resolve(ObjectId.get().toString());
		Files.copy(attachmentFile.toPath(), savePath,
				StandardCopyOption.REPLACE_EXISTING);
		attachment.setSaveTo(FILESYSTEM.name());
		attachment.setSavePath(savePath.toString());
	}

	@Override
	public Attachment updateAttachment(Attachment attachment, File attachmentFile) throws IOException {
		if(FILESYSTEM.name().equalsIgnoreCase(attachment.getSaveTo())
						&& tankProps.isWorkAttachmentSaveToS3()) {
			attachment.setSaveTo(S3.name());
		}
		
		AttachmentBase oldAttachment = getAttachmentBaseById(attachment.getId());
    	Attachment newAttachment = new Attachment();
    	newAttachment.setId(attachment.getId());
    	newAttachment.setSaveTo(attachment.getSaveTo());
    	newAttachment.setDescription(attachment.getDescription());
    	newAttachment.setUploadTime(DateUtil.getTime());
    	Optional<ShiroUser> uploader = ShiroKit.getApiRequestUser();
    	if(uploader.isPresent()) {
    		
    		newAttachment.setUploaderId(uploader.get().getId());
    	}
    	if(attachmentFile == null ) {
    		return updateAttachmentWithoutContent( oldAttachment, newAttachment);
    	}else {
    		return updateAttachmentWithContent(oldAttachment, newAttachment, attachmentFile);
    	}
	}

	private Attachment updateAttachmentWithContent(AttachmentBase oldAttachment,
			Attachment newAttachment, File attachmentFile) throws IOException {
		/*
		 * 上传新的文件内容
		 */
		newAttachment.setFileName(oldAttachment.getFileName());
		newAttachment.setFileType(oldAttachment.getFileType());
		newAttachment.setFileSize(oldAttachment.getFileSize());
		
		/*
		 * 删除原有附件文件
		 */
		if(FILESYSTEM.name().equalsIgnoreCase(oldAttachment.getSaveTo())) {
			Files.deleteIfExists(new File(oldAttachment.getSavePath()).toPath());
		}else if(S3.name().equalsIgnoreCase(oldAttachment.getSaveTo())) {
			awsFileStorage.removeFile(oldAttachment.getSavePath());
		}
		
		return save(newAttachment, attachmentFile);
	}

	private Attachment updateAttachmentWithoutContent( AttachmentBase oldAttachment,
			Attachment newAttachment) throws IOException {
		/*
		 * 未修改附件内容，保持原有内容
		 */
		newAttachment.setFileName(oldAttachment.getFileName());
		newAttachment.setFileType(oldAttachment.getFileType());
		newAttachment.setFileSize(oldAttachment.getFileSize());
		if(newAttachment.getSaveTo().equalsIgnoreCase(oldAttachment.getSaveTo())) {
			newAttachment.setSavePath(oldAttachment.getSavePath());
			newAttachment.setFileContent(getAttachmentById(oldAttachment.getId()).getFileContent());
			return attachmentRepository.save(newAttachment);
		}
		if(DB.name().equalsIgnoreCase(newAttachment.getSaveTo())
				&& oldAttachment.getFileSize()> tankProps.getWorkAttachmentLimitToDb()){
			throw new IOException("attachment is too big to save to db");
		}
		try(InputStream is = getAttachmentStream(oldAttachment.getId())){
			File tempAttachmentFile = File.createTempFile("tempAttachment", "");
			tempAttachmentFile.deleteOnExit();
			Files.copy(is, tempAttachmentFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
			newAttachment = save(newAttachment, tempAttachmentFile);
		}
		/*
		 * 删除原有附件文件
		 */
		if(FILESYSTEM.name().equalsIgnoreCase(oldAttachment.getSaveTo())) {
			Files.deleteIfExists(new File(oldAttachment.getSavePath()).toPath());
		}else if(S3.name().equalsIgnoreCase(oldAttachment.getSaveTo())) {
			awsFileStorage.removeFile(oldAttachment.getSavePath());
		}
		return newAttachment;
	}
	@Override
	public Attachment getAttachmentById(String attachmentId) {
		return attachmentRepository.findById(attachmentId).orElse(null);
	}

	@Override
	public AttachmentBase getAttachmentBaseById(String attachmentId) {
		return attachmentRepository.findOneById(attachmentId);
	}
	@Override
	public void deleteAttachment(String attachmentId) {
		AttachmentBase attachment =  getAttachmentBaseById(attachmentId);
    	if(FILESYSTEM.name().equalsIgnoreCase(attachment.getSaveTo())) {
    		try {
				Files.deleteIfExists(Paths.get(attachment.getSavePath()));
			} catch (Exception e) {
				log.warn("删除附件文件失败",e);
			}
    	}else if(S3.name().equalsIgnoreCase(attachment.getSaveTo())) {
    		try {
    			awsFileStorage.removeFile(attachment.getSavePath());
    		} catch (Exception e) {
    			log.warn("删除附件文件失败",e);
    		}
    		
    	}
    	attachmentRepository.deleteById(attachmentId);
	}

	@Override
	public Attachment updateAttachmentWithoutContent(Attachment attachment) {
		return null;
	}
	
	@Override
	public byte[] getAttachmentContent(String attachmentId) throws IOException {
		Optional<Attachment> attachmentOpt = attachmentRepository.findById(attachmentId);
		byte[] attachmentContent = null;
		if(attachmentOpt.isPresent()) {
			Attachment attachment = attachmentOpt.get();
					
			if (FILESYSTEM.name().equalsIgnoreCase(attachment.getSaveTo())) {
				try {
					attachmentContent = FileUtils.readFileToByteArray(new File(attachment.getSavePath()));
				} catch (IOException e) {
					e.printStackTrace();
				}
			} else if (S3.name().equalsIgnoreCase(attachment.getSaveTo())) {
				attachmentContent = IOUtils.toByteArray(awsFileStorage.openStream(attachment.getSavePath()));
			} else if (DB.name().equalsIgnoreCase(attachment.getSaveTo())) {
				attachmentContent = attachment.getFileContent();
			}
		}
		return attachmentContent;
	}
	
	@Override
	public InputStream getAttachmentStream(String attachmentId)  throws IOException{
		Optional<Attachment> attachmentOpt = attachmentRepository.findById(attachmentId);
		InputStream contentStream = null;
		if(attachmentOpt.isPresent()) {
			Attachment attachment = attachmentOpt.get();
					
			if (FILESYSTEM.name().equalsIgnoreCase(attachment.getSaveTo())) {
				try {
					contentStream = new FileInputStream(new File(attachment.getSavePath()));
				} catch (IOException e) {
					e.printStackTrace();
				}
			} else if (S3.name().equalsIgnoreCase(attachment.getSaveTo())) {
				contentStream = awsFileStorage.openStream(attachment.getSavePath());
			} else if (DB.name().equalsIgnoreCase(attachment.getSaveTo())) {
				contentStream = new ByteArrayInputStream(attachment.getFileContent());
			}
		}
		return contentStream;
	}
	@Override
	public Resource getAttachmentResource(String attachmentId) throws IOException {
		Attachment attachment  = getAttachmentById(attachmentId);
	   	 Resource resource =null;
	   	if(FILESYSTEM.name().equalsIgnoreCase(attachment.getSaveTo())) {
	   		resource = new FileSystemResource(attachment.getSavePath());
	   	}else if (S3.name().equalsIgnoreCase(attachment.getSaveTo())){
	   		resource = new InputStreamResource(awsFileStorage.openStream(attachment.getSavePath()));
	   	}else if (DB.name().equalsIgnoreCase(attachment.getSaveTo())){
	   		resource = new ByteArrayResource(attachment.getFileContent());
	   	}
	   	return resource;
	}
}
